package com.k8.common.util;

import com.k8.common.aop.SpecialAspectConfig;
import com.k8.common.aop.internal.advice.Advice;
import com.k8.common.aop.internal.advice.AdviceOrderEnum;
import com.k8.common.aop.internal.advice.annotation.*;
import com.k8.common.aop.internal.advisor.Advisor;
import com.k8.common.aop.internal.advisor.DefaultAdvisor;
import com.k8.common.aop.internal.exception.PointCutResolveException;
import com.k8.common.aop.internal.interceptor.*;
import com.k8.common.aop.internal.pointcut.DefaultPointCut;
import com.k8.common.aop.internal.pointcut.PointCut;
import com.k8.common.aop.AspectConfig;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.HashSet;
import java.util.Set;

public class AopUtil {

    final static Set<String> objectMethod = new HashSet<>();

    static {
        for (Method method : Object.class.getMethods()) {
            objectMethod.add(method.getName());
        }
    }

    public static boolean isNoOpMethod(Method method) {
        int mod = method.getModifiers();
        if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
            return true;
        } else return objectMethod.contains(method.getName());
    }

    public static void makeAccessible(Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))) {
            method.setAccessible(true);
        }

    }

    public static boolean judgeJoinAdviceArgs(Parameter parameter, AdviceOrderEnum adviceOrderEnum, Method targetMethod) {
        switch (adviceOrderEnum) {
            case After, Around, Before -> {
                return false;
            }
            case AfterReturning -> {
                AfterReturning annotation = targetMethod.getAnnotation(AfterReturning.class);
                String result = annotation.result();
                return result != null && !result.isEmpty() && result.equals(parameter.getName());
            }
            case AfterThrowing -> {
                Throwing annotation = targetMethod.getAnnotation(Throwing.class);
                String result = annotation.throwing();
                return result != null && !result.isEmpty() && result.equals(parameter.getName());
            }
        }
        return false;
    }


    public static Advisor methodToAdvisor(Object aspectConfig, Method method) throws PointCutResolveException {
        if (!(aspectConfig instanceof AspectConfig) && !(aspectConfig instanceof SpecialAspectConfig))
            throw new IllegalArgumentException("AspectConfig not extends form" + AspectConfig.class.getName() + " or " + SpecialAspectConfig.class.getName());
        boolean noOpMethod = isNoOpMethod(method);
        if (noOpMethod) {
            return null;
        }
        Advisor advisor = null;
        if (method.isAnnotationPresent(Around.class)) {
            Around annotation = method.getAnnotation(Around.class);
            String signature = annotation.value();
            PointCut pointCut = new DefaultPointCut(signature);
            Advice advice = new AroundMethodInterceptor(aspectConfig, method);
            advisor = new DefaultAdvisor(pointCut, advice);
        } else if (method.isAnnotationPresent(Before.class)) {
            Before annotation = method.getAnnotation(Before.class);
            String signature = annotation.value();
            PointCut pointCut = new DefaultPointCut(signature);
            Advice advice = new BeforeMethodInterceptor(aspectConfig, method);
            advisor = new DefaultAdvisor(pointCut, advice);
        } else if (method.isAnnotationPresent(AfterReturning.class)) {
            AfterReturning annotation = method.getAnnotation(AfterReturning.class);
            String signature = annotation.value();
            PointCut pointCut = new DefaultPointCut(signature);
            Advice advice = new AfterReturningMethodInterceptor(aspectConfig, method);
            advisor = new DefaultAdvisor(pointCut, advice);
        } else if (method.isAnnotationPresent(Throwing.class)) {
            Throwing annotation = method.getAnnotation(Throwing.class);
            String signature = annotation.value();
            PointCut pointCut = new DefaultPointCut(signature);
            Advice advice = new AfterThrowingMethodInterceptor(aspectConfig, method);
            advisor = new DefaultAdvisor(pointCut, advice);
        } else if (method.isAnnotationPresent(After.class)) {
            After annotation = method.getAnnotation(After.class);
            String signature = annotation.value();
            PointCut pointCut = new DefaultPointCut(signature);
            Advice advice = new AfterMethodInterceptor(aspectConfig, method);
            advisor = new DefaultAdvisor(pointCut, advice);
        }
        return advisor;
    }

    public static CtClass getCtClassForPrimitive(Class<?> clazz, ClassPool pool) {
        if (!clazz.isPrimitive()) {
            try {
                return pool.get(clazz.getName());
            } catch (NotFoundException e) {
                throw new RuntimeException(e);
            }
        }

        if (clazz == int.class) return CtClass.intType;
        else if (clazz == void.class) return CtClass.voidType;
        else if (clazz == boolean.class) return CtClass.booleanType;
        else if (clazz == byte.class) return CtClass.byteType;
        else if (clazz == char.class) return CtClass.charType;
        else if (clazz == short.class) return CtClass.shortType;
        else if (clazz == long.class) return CtClass.longType;
        else if (clazz == float.class) return CtClass.floatType;
        else if (clazz == double.class) return CtClass.doubleType;
        else throw new IllegalArgumentException("Unsupported primitive: " + clazz);
    }

    public static String getDefaultValue(Class<?> primitiveType) {
        if (primitiveType == int.class) {
            return "0";
        } else if (primitiveType == long.class) {
            return "0L";
        } else if (primitiveType == boolean.class) {
            return "false";
        } else if (primitiveType == byte.class) {
            return "(byte)0";
        } else if (primitiveType == short.class) {
            return "(short)0";
        } else if (primitiveType == char.class) {
            return "'\\u0000'";
        } else if (primitiveType == float.class) {
            return "0.0f";
        } else if (primitiveType == double.class) {
            return "0.0d";
        } else if (primitiveType == void.class) {
            return "";
        } else {
            throw new IllegalArgumentException("Unsupported primitive type: " + primitiveType);
        }
    }
}
